﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace UtilZ.Dotnet.Ex.Communication.Net.Web
{
    /// <summary>
    /// 二进制消息
    /// </summary>
    public class WebSocketBinaryMessage : WebSocketMessageAbs
    {
        /// <summary>
        /// 二进制消息数据类型[true:内存Buffer数据;false:文件数据]
        /// </summary>
        private readonly bool _dataType;
        /// <summary>
        /// 二进制消息数据类型[true:内存Buffer数据;false:文件数据]
        /// </summary>
        public bool DataType
        {
            get { return _dataType; }
        }

        private readonly string _filePath = null;
        /// <summary>
        /// 文件
        /// </summary>
        public string FilePath
        {
            get { return _filePath; }
        }


        private readonly byte[] _buffer;
        /// <summary>
        /// 数据buffer
        /// </summary>
        public byte[] Buffer
        {
            get { return _buffer; }
        }

        private readonly long _offset;
        /// <summary>
        /// 在buffer中数据起始位置
        /// </summary>
        public long Offset
        {
            get { return _offset; }
        }

        private readonly long _length;
        /// <summary>
        /// 数据长度
        /// </summary>
        public long Length
        {
            get { return _length; }
        }

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="buffer">数据buffer</param>
        /// <param name="offset">在buffer中数据起始位置,小于等于0表示从0开始</param>
        /// <param name="length">数据长度,小于等于0表示到末尾</param>
        public WebSocketBinaryMessage(byte[] buffer, int offset, int length)
            : base(WebSocketMessageTypes.Binary)
        {
            if (offset < 0)
            {
                offset = 0;
            }

            if (length < 0)
            {
                length = buffer.Length - offset;
            }

            this._dataType = true;
            this._buffer = buffer;
            this._offset = offset;
            this._length = length;
        }

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="filePath">文件</param>
        /// <param name="offset">在文件中数据起始位置,小于等于0表示从0开始</param>
        /// <param name="length">数据长度,小于等于0表示到末尾</param>
        public WebSocketBinaryMessage(string filePath, long offset, long length)
            : base(WebSocketMessageTypes.Binary)
        {
            if (offset < 0)
            {
                offset = 0;
            }

            if (length < 0)
            {
                using (var fs = new FileStream(filePath, FileMode.Open))
                {
                    length = fs.Length - offset;
                }
            }

            this._dataType = false;
            this._filePath = filePath;
            this._offset = offset;
            this._length = length;
        }

        /// <summary>
        /// 获取操作码
        /// </summary>
        /// <returns>操作码</returns>
        protected override byte PrimitiveGetOpcode()
        {
            return HttpOpcodes.BINARY_FRAME;
        }


        /// <summary>
        /// 获取本次发送帧的数据(返回true表示还有数据需要发送,返回false表示最后一帧数据)
        /// </summary>
        /// <param name="frameInfo">帧数据</param>
        /// <returns>true表示还有数据需要发送,返回false表示最后一帧数据</returns>
        protected override void GetFrameData(FrameInfo frameInfo)
        {
            if (this._dataType)
            {
                this.GetFrameDataFromBuffer(frameInfo);
            }
            else
            {
                this.GetFrameDataFromFile(frameInfo);
            }
        }


        private FileStream _fs = null;
        private void GetFrameDataFromFile(FrameInfo frameInfo)
        {
            if (this._fs == null)
            {
                if (string.IsNullOrWhiteSpace(this._filePath) || !File.Exists(this._filePath))
                {
                    frameInfo.FrameIndex = 1;
                    frameInfo.FrameCount = 1;
                    frameInfo.FrameData = null;
                    return;
                }

                this._fs = new FileStream(this._filePath, FileMode.Open, FileAccess.Read);
                this._fs.Seek(this._offset, SeekOrigin.Begin);

                int frameCount = (int)(this._length / frameInfo.FrameMaxSize);
                if (this._length % frameInfo.FrameMaxSize > 0)
                {
                    frameCount++;
                }

                frameInfo.FrameCount = frameCount;
            }

            int frameDataLen = frameInfo.FrameMaxSize;
            long mod = this._length - this._fs.Position;
            if (mod < frameInfo.FrameMaxSize)
            {
                frameDataLen = (int)mod;
            }

            byte[] data = frameInfo.FrameData;
            if (data == null || data.Length < frameDataLen)
            {
                data = new byte[frameDataLen];
            }
            this._fs.Read(data, 0, frameDataLen);
            frameInfo.FrameData = data;
            frameInfo.FrameIndex = (int)(this._fs.Position / frameInfo.FrameMaxSize) + 1;
        }

        private long _sendOffset = -1;
        private void GetFrameDataFromBuffer(FrameInfo frameInfo)
        {
            byte[] data = this._buffer;
            if (data == null)
            {
                frameInfo.FrameIndex = 1;
                frameInfo.FrameCount = 1;
                frameInfo.FrameData = data;
                return;
            }

            if (this._sendOffset < 0)
            {
                this._sendOffset = this._offset;
                int frameCount = (int)this._length / frameInfo.FrameMaxSize;
                if (this._length % frameInfo.FrameMaxSize > 0)
                {
                    frameCount++;
                }

                frameInfo.FrameCount = frameCount;
            }

            int frameDataLen = frameInfo.FrameMaxSize;
            int mod = (int)(this._length - this._sendOffset);
            if (mod < frameInfo.FrameMaxSize)
            {
                frameDataLen = mod;
            }
            frameInfo.FrameData = data.Skip((int)this._sendOffset).Take(frameDataLen).ToArray();
            frameInfo.FrameIndex = (int)this._sendOffset / frameInfo.FrameMaxSize + 1;
            this._sendOffset += frameDataLen;
        }

        /// <summary>
        /// Dispose
        /// </summary>
        /// <param name="disposeing"></param>
        protected override void Dispose(bool disposeing)
        {
            base.Dispose(disposeing);

            if (this._fs != null)
            {
                this._fs.Close();
                this._fs.Dispose();
                this._fs = null;
            }
        }
    }
}
